# -*- coding: utf-8 -*-
#'django-serializable'
#Copyright (C) 2010 Sébastien Piquemal @ futurice
#contact : sebastien.piquemal@futurice.com
#futurice's website : www.futurice.com

#This program is free software: you can redistribute it and/or modify
#it under the terms of the GNU General Public License as published by
#the Free Software Foundation, either version 3 of the License, or
#(at your option) any later version.

#This program is distributed in the hope that it will be useful,
#but WITHOUT ANY WARRANTY; without even the implied warranty of
#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#GNU General Public License for more details.

#You should have received a copy of the GNU General Public License
#along with this program.  If not, see <http://www.gnu.org/licenses/>.
"""
..
    >>> from serializable.models import *

Let’s see quick examples of models using our serialization support.

.. _serialization_examples:

General case
################

Here is some models definition, pay attention to the class attribute `nk_field_names` that corresponds with our natural key : ::

    class Author (models.Serializable) :
        firstname = CharField(max_length=100)
        lastname = CharField(max_length=100)
        
        serialize_fields = ["firstname", "lastname"]
        deserialize_fields = ["firstname", "lastname"]
        nk_field_names = ("firstname", "lastname")

    class Book (models.Serializable) :
        author = ForeignKey(Author)
        title = CharField(max_length=100)
        comments = CharField(max_length=100)
        
        serialize_fields = ["author", "title"]
        deserialize_fields = ["author", "title", "pk"]
        nk_field_names = ("pk",)

Then how to use the serialization features for these objects : 

.. doctest::

    >>> a = Author(firstname="John", lastname="Steinbeck")
    >>> a.save()
    >>> b = Book(title="Grapes of Wrath", author=a, comments="great great great")
    >>> b.save()

.. doctest:: :hide:
    
    natural_key and get_by_natural_key
    -----------------------------------
    >>> a.natural_key()
    ('John', 'Steinbeck')
    >>> Author.objects.get_by_natural_key("John", "Steinbeck").id == a.id
    True

Serializing :

    >>> b.serialize() == {'title': u'Grapes of Wrath', 'author': ('John', 'Steinbeck')}
    True

Deserializing :

    >>> bd = Book.deserialize({'author': ('John', 'Steinbeck'), 'title': 'In Dubious Battle'})
    >>> bd.author.id == a.id
    True
    >>> bd.title
    u'In Dubious Battle'

And updating :
    
    >>> bd.update({'title': 'Of mice and men'})
    >>> bd.title
    u'Of mice and men'
    >>> bd.author == a
    True

So it's very simple... Notice that the foreign key is never directly implied. However, if you want anyways to serialize the primary key of an object or use it as the natural key, use the property `'pk'`.


With a single field as natural key
#####################################

Now if you want to use only one field as the natural key, there is a slight difference. Here's another model : ::

    class Dish (models.Serializable) :
        name = CharField(max_length=100)
        
        nk_field_names = ("name",)

    class Guy (models.Serializable) :
        pseudo = CharField(max_length=100)
        favourite_dish = ForeignKey(Dish)

        serialize_fields = ["pseudo", "favourite_dish"]
        deserialize_fields = ["pseudo", "favourite_dish"]

Some objects :

    >>> d = Dish(name="cheese")
    >>> d.save()
    >>> g = Guy(pseudo="Johnny", favourite_dish=d)
    >>> g.save()

Serializing :

    >>> g.serialize() == {'pseudo': u'Johnny', 'favourite_dish': 'cheese'}
    True

And deserializing :

    >>> gd = Guy.deserialize({'pseudo': u'Jack', 'favourite_dish': 'cheese'})
    >>> gd.favourite_dish.id == d.id
    True
    >>> gd.pseudo
    u'Jack'

That's right ... in case there's only one field as the natural key, you don't have to bother manipulating tuples for (de)serialization operations. Only :attr:`nk_field_names` must still be a tuple. If you want anyways to use a tuple for the deserialization (then with a single element), it will work as well.

Nested relationships / deep serialization
############################################

No matter how deep the relationships are, the natural keys are always used. For example with these models : ::

    class Dynasty(models.Serializable) :
        name = CharField(max_length=100)
        
        serialize_fields = ["name"]
        deserialize_fields = ["name"]
        nk_field_names = ("name",)

    class Family(models.Serializable) :
        name = CharField(max_length=100)
        dynasty = ForeignKey(Dynasty)
        
        serialize_fields = ["name", "dynasty"]
        deserialize_fields = ["name", "dynasty"]
        nk_field_names = ("name", "dynasty")

    class Person(models.Serializable) :
        family = ForeignKey(Family)
        name = CharField(max_length=100)
        
        serialize_fields = ["name", "family"]
        deserialize_fields = ["name", "family"]
        nk_field_names = ("name", "family")
        
    class Short(models.Serializable) :
        person = ForeignKey(Person)
        brand = CharField(max_length=100)
        
        serialize_fields = ["brand", "person"]
        deserialize_fields = ["brand", "person"]
        nk_field_names = ("brand","person")

Some instances :

    >>> d = Dynasty(name="Capetians") ; d.save()
    >>> f = Family(name="Johnson", dynasty=d) ; f.save()
    >>> p = Person(family=f, name="Steven") ; p.save()
    >>> s = Short(person=p, brand="Calvin Klein") ; s.save()

Serializing :

    >>> s.serialize() == {"brand": "Calvin Klein", "person": ("Steven", ("Johnson", "Capetians"))}
    True

Deserializing :
    
    >>> sd = Short.deserialize({"brand": "Pierre Cardin", "person": ("Steven", ("Johnson", "Capetians"))})
    >>> sd.person == p
    True

Serializing/deserializing properties
######################################

The (de)serialization works exactly the same with all the properties (notably with the `'pk'` property) : ::

    class PropertyFields (models.Serializable) :
        deserialize_fields = ["a_field_property"]
        serialize_fields = ['a_field_property']
        
        a_field = CharField(max_length=100)
        
        @property
        def a_field_property(self):
            return self.a_field

        @a_field_property.setter
        def a_field_property(self, value):
            self.a_field = value

Some objects :

    >>> pf = PropertyFields(a_field='test1')

Serializing :

    >>> pf.serialize() == {'a_field_property': 'test1'}
    True
    
Deserializing :

    >>> pf = PropertyFields.deserialize({"a_field_property": "test test test"})
    >>> pf.a_field_property
    'test test test'
    >>> pf.a_field
    'test test test'

Using mixins instead of :class:`Serializable` as a base class
###############################################################

If you don't want to use :class:`Serializable` as a base class for your models, you should use :class:`SerializableMixin` as a mixin. For example : ::

    class MyModel(MyBaseModel, SerializableMixin):
        
        #default manager must have a 'get_by_natural_key' method
        objects = SerializableManager()
        
        #your code here

If you don't want to use :class:`SerializableManager` as your default manager neither, you can also define your custom manager and use the :class:`:class:`SerializableManagerMixin`. For example : ::

    class MyManager(MyBaseManager, SerializableManagerMixin):
        
        #your code here


..  OTHER TESTS
    #############################

    with a singleton as NK
    -------------------------
    >>> gd = Guy.deserialize({'pseudo': u'Jack', 'favourite_dish': ('cheese',)})
    >>> gd.favourite_dish.id == d.id
    True
    >>> gd.pseudo
    u'Jack'


    with an invalid NK
    --------------------
    >>> gd = Guy.deserialize({'pseudo': u'Jack', 'favourite_dish': ('cheese','chaud')})#doctest: +IGNORE_EXCEPTION_DETAIL
    Traceback (most recent call last):
        ...
    DeserializationError:


    ----- Fixture
    >>> d2 = Dish(name="cheese")
    >>> d2.save()

    With a NK that yields two objects
    -----------------------------------
    >>> Guy.deserialize({'pseudo': u'Jimmy', 'favourite_dish': 'cheese'})#doctest: +IGNORE_EXCEPTION_DETAIL
    Traceback (most recent call last):
        ...
    MultipleObjectsReturned:
    

    With a NK that doesn't yield any object
    -----------------------------------------
    >>> Guy.deserialize({'pseudo': u'Jack', 'favourite_dish': 'nacho'})#doctest: +IGNORE_EXCEPTION_DETAIL
    Traceback (most recent call last):
        ...
    DoesNotExist:


    ----- Fixture
    >>> t = Train (name="TGV")
    >>> t2 = Train (name="Thalys")
    >>> t.save() ; t2.save()
    >>> w = Wagon(train=t)
    >>> w.save()
    
    With a foreign class that doesn't inherit from Serializable
    --------------------------------------------------------------
    >>> w.serialize() == {"train": t.id}
    True
    >>> w2 = Wagon.deserialize({"train": t2.id})
    >>> isinstance(w2, Wagon)
    True
    >>> w2.train == t2
    True
    
    Fixture
    ----------
    >>> j = JustId()
    >>> j.save()

    serialize pk
    -------------
    >>> j.serialize() == {"pk": j.pk}
    True

    deserialize pk
    ----------------
    >>> j = JustId.deserialize({"pk": 8888})
    >>> isinstance(j, JustId)
    True
    >>> j.pk
    8888

    ----- Fixture
    >>> t = Train (name="ICE")
    >>> t.save()
    >>> w = Wagon(train=t)
    >>> w.save()
    >>> s = Seat(wagon=w)
    >>> s.save()

    Foreign serialized as pk
    --------------------------
    >>> s.serialize() == {'wagon': w.pk}
    True

    Foreign key deserialized using its pk
    ---------------------------------------
    >>> sd = Seat.deserialize({'wagon': w.pk})
    >>> isinstance(sd, Seat)
    True
    >>> sd.wagon.pk == w.pk
    True

    ----- Cleaning-up
    >>> dummy = [a.delete() for a in Author.objects.all()]
    >>> dummy = [b.delete() for b in Book.objects.all()]
"""

# -*- coding: utf-8 -*-
from django.db.models import ForeignKey, CharField, Model
from serializable import models

class Author (models.Serializable) :
    firstname = CharField(max_length=100)
    lastname = CharField(max_length=100)
    
    serialize_fields = ["firstname", "lastname"]
    deserialize_fields = ["firstname", "lastname"]
    nk_field_names = ("firstname", "lastname")

class Book (models.Serializable) :
    author = ForeignKey(Author)
    title = CharField(max_length=100)
    comments = CharField(max_length=100)
    
    serialize_fields = ["author", "title"]
    deserialize_fields = ["author", "title", "pk"]
    nk_field_names = ("pk",)

class Dish (models.Serializable) :
    name = CharField(max_length=100)
    
    nk_field_names = ("name",)

class Guy (models.Serializable) :
    pseudo = CharField(max_length=100)
    favourite_dish = ForeignKey(Dish)

    serialize_fields = ["pseudo", "favourite_dish"]
    deserialize_fields = ["pseudo", "favourite_dish"]
    
class Train (Model) :
    name = CharField(max_length=100)

class Wagon (models.Serializable) :
    train = ForeignKey(Train)

    serialize_fields = ["train"]
    deserialize_fields = ["train"]

    nk_field_names = ("pk",)

class Seat (models.Serializable) :
    wagon = ForeignKey(Wagon)

    serialize_fields = ["wagon"]
    deserialize_fields = ["wagon"]

class JustId (models.Serializable) :
    serialize_fields = ["pk"]
    deserialize_fields = ["pk"]

class PropertyFields (models.Serializable) :
    deserialize_fields = ["a_field_property"]
    serialize_fields = ['a_field_property']
    
    a_field = CharField(max_length=100)
    
    @property
    def a_field_property(self):
        return self.a_field

    @a_field_property.setter
    def a_field_property(self, value):
        self.a_field = value

class Dynasty(models.Serializable) :
    name = CharField(max_length=100)
    
    serialize_fields = ["name"]
    deserialize_fields = ["name"]
    nk_field_names = ("name",)

class Family(models.Serializable) :
    name = CharField(max_length=100)
    dynasty = ForeignKey(Dynasty)
    
    serialize_fields = ["name", "dynasty"]
    deserialize_fields = ["name", "dynasty"]
    nk_field_names = ("name", "dynasty")

class Person(models.Serializable) :
    family = ForeignKey(Family)
    name = CharField(max_length=100)
    
    serialize_fields = ["name", "family"]
    deserialize_fields = ["name", "family"]
    nk_field_names = ("name", "family")
    
class Short(models.Serializable) :
    person = ForeignKey(Person)
    brand = CharField(max_length=100)
    
    serialize_fields = ["brand", "person"]
    deserialize_fields = ["brand", "person"]
    nk_field_names = ("brand","person")
